﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;

using hellaSharp;
using CookComputing.XmlRpc;

using agsXMPP;
using agsXMPP.protocol;
using agsXMPP.protocol.client;
using agsXMPP.protocol.iq.disco;
using agsXMPP.protocol.extensions.caps;
using agsXMPP.protocol.extensions.si;
using agsXMPP.protocol.extensions.filetransfer;
using agsXMPP.protocol.extensions.featureneg;
using agsXMPP.protocol.extensions.bytestreams;
using agsXMPP.protocol.x.data;

/*
 * Taken from agsXMPP mini client example code and modified
 */


namespace hellaBot
{
    class FileTransfer
    {
        // Add here your file transfer proxy, or disover it with service discovery
        // DONT USE THIS PROXY FOR PRODUCTION. THIS PROXY IS FOR RESTING ONLY. THIS PROXY IS ALSO NOT RUNNING ALL THE TIME
        // Install your own server with bytestream proxy or the external proxy65 module
        // const string PROXY = "proxy.ag-software.de";
        //const string PROXY = "proxy.netlab.cz";
        // const string PROXY = "proxy.jabber.org";        
        const string PROXY = "v-jabberd.gradwell.net";

        /// <summary>
        /// SID of the filetransfer
        /// </summary>
        private string m_Sid;
        private Jid m_From;
        private DateTime m_startDateTime;

        private JEP65Socket _proxySocks5Socket;

        private XmppClientConnection m_XmppCon;

        private long m_lFileLength;
        private long m_bytesTransmitted = 0;
        private FileStream m_FileStream;

        agsXMPP.protocol.extensions.filetransfer.File file;
        agsXMPP.protocol.extensions.si.SI si;
        IQ siIq;

        public FileTransfer(XmppClientConnection XmppCon, IQ iq)
        {
            Console.WriteLine("Accepting a file from " + iq.From.ToString());

            siIq = iq;
            si = iq.SelectSingleElement(typeof(agsXMPP.protocol.extensions.si.SI)) as agsXMPP.protocol.extensions.si.SI;
            // get SID for file transfer
            m_Sid = si.Id;
            m_From = iq.From;

            file = si.File;

            m_XmppCon = XmppCon;

            XmppCon.OnIq += new IqHandler(XmppCon_OnIq);

            agsXMPP.protocol.extensions.featureneg.FeatureNeg fNeg = si.FeatureNeg;
            if (fNeg != null)
            {
                agsXMPP.protocol.x.data.Data data = fNeg.Data;
                if (data != null)
                {
                    agsXMPP.protocol.x.data.Field[] field = data.GetFields();
                    if (field.Length == 1)
                    {
                        Dictionary<string, string> methods = new Dictionary<string, string>();
                        foreach (agsXMPP.protocol.x.data.Option o in field[0].GetOptions())
                        {
                            string val = o.GetValue();
                            methods.Add(val, val);
                        }
                        if (methods.ContainsKey(agsXMPP.Uri.BYTESTREAMS))
                        {
                            // supports bytestream, so choose this option
                            agsXMPP.protocol.extensions.si.SIIq sIq = new agsXMPP.protocol.extensions.si.SIIq();
                            sIq.Id = siIq.Id;
                            sIq.To = siIq.From;
                            sIq.Type = IqType.result;

                            sIq.SI.Id = si.Id;
                            sIq.SI.FeatureNeg = new agsXMPP.protocol.extensions.featureneg.FeatureNeg();

                            Data xdata = new Data();
                            xdata.Type = XDataFormType.submit;
                            Field f = new Field();
                            //f.Type = FieldType.List_Single;
                            f.Var = "stream-method";
                            f.AddValue(agsXMPP.Uri.BYTESTREAMS);
                            xdata.AddField(f);
                            sIq.SI.FeatureNeg.Data = xdata;

                            m_XmppCon.Send(sIq);
                        }
                    }
                }
            }
        }

        private void XmppCon_OnIq(object sender, agsXMPP.protocol.client.IQ iq)
        {
            if (iq.Query != null && iq.Query.GetType() == typeof(agsXMPP.protocol.extensions.bytestreams.ByteStream))
            {
                agsXMPP.protocol.extensions.bytestreams.ByteStream bs = iq.Query as agsXMPP.protocol.extensions.bytestreams.ByteStream;
                // check is this is for the correct file
                if (bs.Sid == m_Sid)
                {
                    Thread t = new Thread(
                        delegate() { HandleStreamHost(bs, iq); }
                    );
                    t.Name = "LoopStreamHosts";
                    t.Start();
                }

            }
        }

        private void HandleStreamHost(ByteStream bs, IQ iq)
        //private void HandleStreamHost(object obj)
        {
            //IQ iq = obj as IQ;
            //ByteStream bs = iq.Query as agsXMPP.protocol.extensions.bytestreams.ByteStream;;
            //ByteStream bs = iq.Query as ByteStream;
            if (bs != null)
            {
                _proxySocks5Socket = new JEP65Socket();
                _proxySocks5Socket.OnConnect += new ObjectHandler(m_s5Sock_OnConnect);
                _proxySocks5Socket.OnReceive += new agsXMPP.net.BaseSocket.OnSocketDataHandler(m_s5Sock_OnReceive);
                _proxySocks5Socket.OnDisconnect += new ObjectHandler(m_s5Sock_OnDisconnect);

                StreamHost[] streamhosts = bs.GetStreamHosts();
                //Scroll through the possible sock5 servers and try to connect
                //foreach (StreamHost sh in streamhosts)
                //this is done back to front in order to make sure that the proxy JID is chosen first
                //this is necessary as at this stage the application only knows how to connect to a 
                //socks 5 proxy.

                foreach (StreamHost sHost in streamhosts)
                {
                    if (sHost.Host != null)
                    {
                        _proxySocks5Socket.Address = sHost.Host;
                        _proxySocks5Socket.Port = sHost.Port;
                        _proxySocks5Socket.Target = m_XmppCon.MyJID;
                        _proxySocks5Socket.Initiator = m_From;
                        _proxySocks5Socket.SID = m_Sid;
                        _proxySocks5Socket.ConnectTimeout = 5000;
                        _proxySocks5Socket.SyncConnect();
                        if (_proxySocks5Socket.Connected)
                        {
                            SendStreamHostUsedResponse(sHost, iq);
                            break;
                        }
                    }
                }

            }
        }

        private void m_s5Sock_OnDisconnect(object sender)
        {
            m_FileStream.Close();
            m_FileStream.Dispose();

            if (m_bytesTransmitted == m_lFileLength)
            {
                // completed
                // tslTransmitted.Text = "completed";
                Console.WriteLine("Finished file transfer");
            }
            else
            {
                // not complete, some error occured or somebody canceled the transfer

            }
        }

        void m_s5Sock_OnReceive(object sender, byte[] data, int count)
        {
            m_FileStream.Write(data, 0, count);

            m_bytesTransmitted += count;


            // Windows Forms are not Thread Safe, we need to invoke this :(
            // We're not in the UI thread, so we need to call BeginInvoke
            // to udate the progress bar	
            /*TimeSpan ts = DateTime.Now - m_lastProgressUpdate;
            if (ts.Seconds >= 1)
            {
                BeginInvoke(new ObjectHandler(UpdateProgress), new object[] { sender });
            }*/
            Console.WriteLine("Got data");
        }

        void m_s5Sock_OnConnect(object sender)
        {
            m_startDateTime = DateTime.Now;

            // string path = Util.AppPath + @"\Received Files";
            string path = @"C:\";
            System.IO.Directory.CreateDirectory(path);

            m_FileStream = new FileStream(Path.Combine(path, file.Name), FileMode.Create);

            //throw new Exception("The method or operation is not implemented.");
        }

        private void SendStreamHostUsedResponse(StreamHost sh, IQ iq)
        {
            ByteStreamIq bsIQ = new ByteStreamIq(IqType.result, m_From);
            bsIQ.Id = iq.Id;

            bsIQ.Query.StreamHostUsed = new StreamHostUsed(sh.Jid);
            m_XmppCon.Send(bsIQ);

        }
    }
}
